package org.springframework.extcache.localcache;

import java.sql.Timestamp;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;

import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.extcache.AbstractConfiggurableCache;

class ConcurrentMapCache extends AbstractConfiggurableCache {

  private ConcurrentMap<String, Object> store;
  private boolean                       isPutable;
  private boolean                       isLoaded;
  private CacheObject                   cacheObject = new CacheObject();

  ConcurrentMapCache() {
  }

  ConcurrentMapCache(String name, ConcurrentMap<String, Object> store, boolean isPutable) {
    this.name = name;
    this.store = store;
    this.isPutable = isPutable;
  }

  @Override
  public Object getNativeCache() {
    return store;
  }

  @Override
  public ValueWrapper get(Object key) {
    init(key);
    Object cacheObjectValue = store.get(getActualKey());
    if (cacheObjectValue instanceof CacheObject) {
      CacheObject cacheObject = (CacheObject) cacheObjectValue;
      setVersion(cacheObject.version);
      this.value = cacheObject.getValue();
      this.cacheObject.setCreateTime(cacheObject.getCreateTime());
      this.cacheObject.setValue(cacheObject.getValue());
      this.cacheObject.setVersion(cacheObject.getVersion());
    }

    ValueWrapper valueWrapper = value == null ? null : new SimpleValueWrapper(value);
    isLoaded = true;
    this.value = valueWrapper == null ? null : valueWrapper.get();
    return valueWrapper;
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T get(Object key, Class<T> type) {
    ValueWrapper valueWrapper = get(key);
    if (valueWrapper == null) {
      return null;
    }
    Object cacheObjectVal = valueWrapper.get();
    if (cacheObjectVal instanceof CacheObject) {
      CacheObject cacheObject = (CacheObject) cacheObjectVal;
      return cacheObject == null ? null : (T) cacheObject.getValue();
    }
    return null;
  }

  @Override
  public void put(Object key, Object value) {
    if (!isPutable) {
      return;
    }
    boolean inited = init(key);
    if (inited) {
      String actualKey = getActualKey();
      CacheObject cacheObject = new CacheObject(value, getVersion());
      this.cacheObject.setCreateTime(cacheObject.getCreateTime());
      this.cacheObject.setValue(value);
      this.cacheObject.setVersion(cacheObject.getVersion());
      store.put(actualKey, cacheObject);
      this.value = value;
      isLoaded = true;
    }
  }

  @Override
  public void evict(Object key) {
    init(key);
    store.remove(getActualKey());
    isLoaded = false;
    this.value = null;
  }

  @Override
  public void clear() {
    store.clear();
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T get(Object key, Callable<T> valueLoader) {
    init(key);
    return (T) store.get(getActualKey());
  }

  @Override
  public ValueWrapper putIfAbsent(Object key, Object value) {
    init(key);
    String actualKey = getActualKey();
    ValueWrapper cacheVal = get(key);
    if (cacheVal == null) {
      put(actualKey, value);
    }
    return cacheVal;
  }

  static class CacheObject {
    Timestamp createTime;
    Object    value;
    String    version;

    CacheObject() {
    }

    CacheObject(Object value, String version) {
      this.value = value;
      this.version = version;
      this.createTime = new Timestamp(System.currentTimeMillis());
    }

    public Timestamp getCreateTime() {
      return createTime;
    }

    public void setCreateTime(Timestamp createTime) {
      this.createTime = createTime;
    }

    public Object getValue() {
      return value;
    }

    public void setValue(Object value) {
      this.value = value;
    }

    public String getVersion() {
      return version;
    }

    public void setVersion(String version) {
      this.version = version;
    }

  }

  @Override
  public boolean isLoaded() {
    return isLoaded;
  }

  @Override
  public String getVersion() {
    return version;
  }

  @Override
  public void setVersion(String version) {
    this.version = version;
    this.cacheObject.setValue(version);
  }

}
